﻿using System;
using System.Linq;
using System.CodeDom;
using System.Reflection;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Skynet.Web
{
    public class NetBuildManager
    {
        public static Type CreateProxy(Type interfaceType, string proxyClassName)
        {
            var proxyClass = new CodeTypeDeclaration(proxyClassName)
            {
                IsClass = true,
                TypeAttributes = TypeAttributes.Public,
            };
            proxyClass.BaseTypes.Add(new CodeTypeReference(interfaceType));
            AddMethods(interfaceType, proxyClass);
            //
            var namespaces = new CodeNamespace(interfaceType.Namespace);
            namespaces.Imports.Add(new CodeNamespaceImport("System"));
            namespaces.Types.Add(proxyClass);
            //
            var unit = new CodeCompileUnit();
            unit.Namespaces.Add(namespaces);
            // Compile in-memory assembly with C# 4.0 compiler.
            var provider = CodeDomProvider.CreateProvider("csharp", new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
            var referencedAssemblies = new StringCollection();
            var options = new CompilerParameters
            {
                CompilerOptions = "/target:library /optimize",
                OutputAssembly = @"C:\T_\test.dll",
                GenerateExecutable = false,
                GenerateInMemory = true,
                IncludeDebugInformation = false,
            };
            options.ReferencedAssemblies.Add(interfaceType.Assembly.ManifestModule.FullyQualifiedName);
            var results = provider.CompileAssemblyFromDom(options, unit);
            if (results.Errors.HasErrors)
            {
                // If error throw ex with compiler errors in ex.Data.
                var ex = new InvalidOperationException("Cannot create proxy. See provided data 'compilerErrors' for details.");
                ex.Data["compilerErrors"] = results.Errors;
                throw ex;
            }
            // Compiled assembly has the generated proxy. Done.
            return results.CompiledAssembly.GetType(interfaceType.Namespace + '.' + proxyClassName);
        }

        private static void AddMethods(Type interfaceType, CodeTypeDeclaration proxyClass)
        {
            foreach (var mi in interfaceType.GetMethods()
                .Where(mi => (mi.Attributes & MethodAttributes.SpecialName) != MethodAttributes.SpecialName))
            {
                var method = new CodeMemberMethod
                {
                    Attributes = MemberAttributes.Public | MemberAttributes.Final,
                    Name = mi.Name,
                    ReturnType = new CodeTypeReference(mi.ReturnType),
                };
                foreach (var par in mi.GetParameters())
                    method.Parameters.Add(new CodeParameterDeclarationExpression(par.ParameterType, par.Name));
                var objRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "obj");
                var invoke = new CodeMethodInvokeExpression(objRef, mi.Name, mi.GetParameters().Select(p => new CodeArgumentReferenceExpression(p.Name)).ToArray());
                if (mi.ReturnType == typeof(void))
                    method.Statements.Add(invoke);
                else
                    method.Statements.Add(new CodeMethodReturnStatement(invoke));
                proxyClass.Members.Add(method);
            }
        }
    }
}
